#include "nnsliteheapfile.h"

/* Definitions of macros to access the code of page state */
#define NNSLITE_PAGE_SET_USED(state)    ((state) |= 0x80000000)
#define NNSLITE_PAGE_SET_NOTUSED(state) ((state) &= 0x7FFFFFFF)
#define NNSLITE_PAGE_SET_NEXT_NULL(state) ((state) &= 0x80000000)
#define NNSLITE_PAGE_SET_NEXTPAGE(state,next) ((state) = 0x80000000|((next)+1))
#define NNSLITE_PAGE_NEXT_PAGE(state)  (((state) & 0x7FFFFFFF) - 1)
#define NNSLITE_PAGE_IS_USED(state)  (((state) & 0x80000000) == 0x80000000)
#define NNSLITE_PAGE_HAS_NEXT(state) (((state) & 0x7FFFFFFF) != 0x0)

static NNSLITE_RET FindFreePage(nnsliteHeapFS* heapFS, u32* pageindex)
{
	u32  i=0;
	nnsliteHeapPageHeader* PAT = heapFS->pageAllocationTable;
	for (i=0;i<heapFS->numPages; i++)
	{
		if (NNSLITE_PAGE_IS_USED(PAT[i].state) == FALSE)
		{
			*pageindex = i;
			return NNSLITE_RET_OK;
		}
	}
	return NNSLITE_RET_ERR_NO_MORE_SPACE;
}

static NNSLITE_RET PageWrite(nnsliteHeapFS* heapFS, u32 pageIndex, u32 offset, const void* buf, u32 size)
{
	u64 totalOffset = heapFS->pageStoreOffset;
	totalOffset += pageIndex * heapFS->pageSize + offset;
	fwrite(buf, size, 1, heapFS->fp);
	return NNSLITE_RET_OK;
}

static u32 PageRead(nnsliteHeapFS* heapFS, u32 pageIndex, u32 offset, void* buf, u32 size)
{
	u64 totalOffset = heapFS->pageStoreOffset;
	totalOffset += pageIndex * heapFS->pageSize + offset;
	fread(buf, size, 1, heapFS->fp);
	return NNSLITE_RET_OK;
}

static u32 GetPageIndex(nnsliteHeapFile* heapFile, u32 pageIndexOfFile)
{
	nnsliteHeapPageHeader* PAT = heapFile->heapFS->pageAllocationTable;
	u32 pageIndex = heapFile->startPageIndex;
	while(pageIndexOfFile > 0)
	{
		pageIndex = NNSLITE_PAGE_NEXT_PAGE(PAT[pageIndex].state);
		pageIndexOfFile--;
	}
	return pageIndex;
}


NNSLITE_RET nnsliteHeapFSInit(nnsliteHeapFS* heapFS, const char* filename, u32 pagesize, u32 numpages)
{
	heapFS->fp = NULL;
	strcpy(heapFS->filename, filename);
	heapFS->pageSize = pagesize;
	heapFS->numPages = numpages;
	heapFS->pageAllocationTable = (nnsliteHeapPageHeader*)malloc(numpages*sizeof(nnsliteHeapPageHeader));
	heapFS->pageStoreOffset = 0;
	memset(heapFS->pageAllocationTable, 0, numpages*sizeof(nnsliteHeapPageHeader));
	memset(heapFS->files, 0, NNSLITE_MAX_NUM_DATAPAGE*sizeof(nnsliteHeapFile));
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFSFree(nnsliteHeapFS* heapFS)
{
	if (heapFS->fp != NULL)
		fclose(heapFS->fp);
	heapFS->fp = NULL;

	if (heapFS->pageAllocationTable != NULL)
		free(heapFS->pageAllocationTable);
	heapFS->pageAllocationTable = NULL;

	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileOpenNew(nnsliteHeapFS* heapFS, nnsliteHeapFile** heapFile)
{
	NNSLITE_RET ret;
	u32 freePageIndex;
	u32 i;

	ret = FindFreePage(heapFS, &freePageIndex);
	if (NNSLITE_RET_OK != ret)
	{
		assert(FALSE);
		return ret;
	}

	for (i=0; i<NNSLITE_MAX_NUM_DATAPAGE; i++)
	{
		if (heapFS->files[i].isUsed == FALSE)
		{
			*heapFile = &heapFS->files[i];
			(*heapFile)->isUsed = TRUE;
			(*heapFile)->fileNo = i;
			(*heapFile)->startPageIndex = freePageIndex;
			(*heapFile)->lastPageIndex = freePageIndex;
			(*heapFile)->heapFS = heapFS;
			(*heapFile)->capacity = 0;
			(*heapFile)->length = 0;
			break;
		}
	}

	if (i == NNSLITE_MAX_NUM_DATAPAGE)
	{
		assert(FALSE);
		return NNSLITE_RET_ERR_DATAPAGE_OUTOFLIMIT;
	}
	
	/* set this page to be used */
	NNSLITE_PAGE_SET_USED(heapFS->pageAllocationTable[freePageIndex].state);

	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileOpen(nnsliteHeapFS* heapFS, u32 fileNo, nnsliteHeapFile** heapFile)
{

}

NNSLITE_RET nnsliteHeapFileClose(nnsliteHeapFile* heapFile)
{
	u32 pageState;
	u32 pageIndex;
	nnsliteHeapFS* heapFS = heapFile->heapFS;

	heapFile->isUsed = FALSE;
	heapFile->fileNo = 0;
	
	/* free all the files in the page allocation table */
	pageIndex = heapFile->startPageIndex;
	pageState = heapFS->pageAllocationTable[pageIndex].state;
	NNSLITE_PAGE_SET_NOTUSED(heapFS->pageAllocationTable[pageIndex].state);
	while(NNSLITE_PAGE_HAS_NEXT(pageState))
	{
		pageIndex = NNSLITE_PAGE_NEXT_PAGE(pageState);
		pageState = heapFS->pageAllocationTable[pageIndex].state;
		NNSLITE_PAGE_SET_NOTUSED(heapFS->pageAllocationTable[pageIndex].state);
	}

	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileWrite(nnsliteHeapFile* heapFile, const void* buf, u64 size)
{
	NNSLITE_RET ret;
	u8* byteBuf = (u8*)buf;
	nnsliteHeapFS* heapFS = heapFile->heapFS;
	nnsliteHeapPageHeader* PAT = heapFS->pageAllocationTable;
	u32 pageIndex = heapFile->lastPageIndex;
	u32 freeSizeOfPage = heapFS->pageSize - PAT[pageIndex].usedSize;
	u32 nextPageIndex;

	if (freeSizeOfPage == 0)
	{
		/* allocate new free page to this file */
		ret = FindFreePage(heapFS, &nextPageIndex);
		if (NNSLITE_FAILED(ret))
		{
			assert(FALSE);
			return ret;
		}
		NNSLITE_PAGE_SET_NEXTPAGE(PAT[pageIndex].state, nextPageIndex);
		pageIndex = nextPageIndex;
		freeSizeOfPage = heapFS->pageSize;
	}

	while (size > freeSizeOfPage)
	{
		/* fill the rest of page with the data */
		ret = PageWrite(heapFS, pageIndex, PAT[pageIndex].usedSize, byteBuf, freeSizeOfPage);
		NNSLITE_ON_FAILED_RETURN(ret);
		heapFile->length += freeSizeOfPage;
		byteBuf += freeSizeOfPage;
		size -= freeSizeOfPage;

		/* allocate new free page to this file */
		ret = FindFreePage(heapFS, &nextPageIndex);
		NNSLITE_ON_FAILED_RETURN(ret);
		NNSLITE_PAGE_SET_NEXTPAGE(PAT[pageIndex].state, nextPageIndex);
		pageIndex = nextPageIndex;
		freeSizeOfPage = heapFS->pageSize;
	}

	if (size > 0)
	{
		/* fill the rest of page with the data */
		PageWrite(heapFS, pageIndex, PAT[pageIndex].usedSize, byteBuf, (u32)size);
		heapFile->length += size;
	}

	heapFile->lastPageIndex = pageIndex;
	return NNSLITE_RET_OK;
}

NNSLITE_RET nnsliteHeapFileRead(nnsliteHeapFile* heapFile, void* buf, u64 offset, u64 size)
{
	NNSLITE_RET ret;
	u8* byteBuf = (u8*)buf;
	nnsliteHeapFS* heapFS = heapFile->heapFS;
	nnsliteHeapPageHeader* PAT = heapFS->pageAllocationTable;
	u32 pageIndexOfFile = (u32)(offset / heapFS->pageSize);
	u32 pageIndex = GetPageIndex(heapFile, pageIndexOfFile);
	u32 nextPageIndex;
	u32 offsetInPage = (u32)(offset - pageIndexOfFile*heapFS->pageSize);
	u32 sizeCanRead = PAT[pageIndex].usedSize - offsetInPage;

	while(size > sizeCanRead)
	{
		/* read the rest of the page */
		ret = PageRead(heapFS, pageIndex, offsetInPage, byteBuf, sizeCanRead);
		NNSLITE_ON_FAILED_RETURN(ret);
		byteBuf += sizeCanRead;
		size -= sizeCanRead;

		/* move to the next page of the file */
		nextPageIndex = NNSLITE_PAGE_NEXT_PAGE(PAT[pageIndex].state);
		pageIndex = nextPageIndex;
		offsetInPage = 0;
		sizeCanRead = PAT[pageIndex].usedSize;
	}

	if (size > 0)
	{
		/* read the rest of the page */
		PageRead(heapFS, pageIndex, offsetInPage, byteBuf, (u32)size);
	}

	return NNSLITE_RET_OK;
}

